﻿using System;
using System.Linq;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Lucene.Net.Search;
using Lucene.Net.Analysis;
using Lucene.Net.Documents;
using Ayatta.Contract.Domain;
using System.Collections.Generic;
using Ayatta.Contract.Component;
using Ayatta.Service.Product.Index.Domain;
using Ayatta.Service.Product.Index.Extension;
using Lucene.Net.QueryParsers;
using IKAnalyzer.Net.Lucene;
using System.Data.SqlClient;
using System.Data;
using System.IO;
using Lucene.Net.Analysis.Tokenattributes;
using Lucene.Net.Search.Highlight;

namespace Ayatta.Service.Product.Index
{
    /// <summary>
    /// 商品索引器
    /// </summary>
    internal sealed class ProductIndexer : LuceneIndexer<ProductItem>
    {
        /// <summary>
        /// 搜索参数中是否有分类Id
        /// </summary>
        //private ProductSearchParam param;

        /// <summary>
        /// 索引器单例
        /// </summary>
        public static readonly ProductIndexer Instance = new ProductIndexer(AddDocument, ConvertDocument);

        private ProductIndexer(Action<IndexDirectory> action, Func<Document, ProductItem> func)
            : base(action, func)
        {

        }

        public PagedList<ProductItem> Query(ProductQueryParam param)
        {
            return Query(ConvertParam(param));
        }

        public PagedList<ProductItem, ProductQueryStatistics> Search(ProductQueryParam param)
        {

            var result = Query(param);
            //var extra=
            //var s = FieldCache_Fields.DEFAULT.GetStrings(Searcher.IndexReader, "brand");
            return new PagedList<ProductItem, ProductQueryStatistics>(result.Data, result.Total);
        }


        /// <summary>
        /// 生成索引主方法
        /// </summary>
        /// <param name="target">索引目录</param>
        private static void AddDocument(IndexDirectory target)
        {
            var document = new Document();

            document.Add(new Field("Id", "", Field.Store.YES, Field.Index.NO));
            document.Add(new Field("Name", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            document.Add(new Field("BrandId", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            document.Add(new Field("BrandName", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            //document.Add(new Field("CateId", "", Field.Store.YES, Field.Index.ANALYZED));
            //document.Add(new Field("CateName", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            document.Add(new Field("PropId", "", Field.Store.YES, Field.Index.ANALYZED));
            //document.Add(new Field("PropName", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            document.Add(new Field("Participle", "", Field.Store.YES, Field.Index.ANALYZED));
            //document.Add(new Field("Boost", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            //document.Add(new Field("Price", "", Field.Store.YES, Field.Index.NOT_ANALYZED));
            //document.Add(new Field("SaleQuantity", "", Field.Store.YES, Field.Index.NOT_ANALYZED));

            var path = target.Path();
            var directory = FSDirectory.Open(path);
            using (var writer = new IndexWriter(directory, new WhitespaceAnalyzer(), true, IndexWriter.MaxFieldLength.UNLIMITED))
            {
                AddDocument(writer, document);
                writer.Optimize();
                writer.Commit();
            }
        }

        private static void AddDocument(IndexWriter writer, Document document)
        {
            var skip = 0;
            var take = 200;
            var connectionString = "server=.;database=AyattaStore;integrated security=true;";

            using (var connection = new SqlConnection(connectionString))
            {
                connection.Open();
                while (true)
                {
                    var begin = skip + 1;
                    var end = skip + take;
                    var queryString = @"select * from (
                    select [Id],[BrandId],[BrandName],[Name],[PropId],
                    row_number() over(order by id) row from [Product.Item] where 1=1
                    )a where row between " + begin + " and " + end;
                    using (var command = new SqlCommand(queryString, connection))
                    using (var reader = command.ExecuteReader())
                    {
                        if (reader.HasRows)
                        {
                            while (reader.Read())
                            {
                                SetDocument(reader, document);
                                writer.AddDocument(document);
                            }
                            skip += take;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }
        }

        private static void SetDocument(IDataReader reader, Document document)
        {
            var itemName = string.Empty;
            var brandName = string.Empty;
            var categoryName = string.Empty;


            var name = "Id";
            var value = reader[name].ToString();
            document.GetField(name).SetValue(value);

            name = "Name";
            itemName = value = reader[name].ToString();
            document.GetField(name).SetValue(value);

            name = "BrandId";
            value = reader[name].ToString();
            document.GetField(name).SetValue(value);

            name = "BrandName";
            brandName = value = reader[name].ToString();
            document.GetField(name).SetValue(value);

            name = "PropId";
            value = reader[name].ToString().Replace(";", " ");//空格分词
            document.GetField(name).SetValue(value);

            name = "Participle";
            value = GetParticiple(itemName, brandName, categoryName);
            document.GetField(name).SetValue(value);
        }

        private static string GetParticiple(string itemName, string brandName, string categoryName)
        {
            var array = new List<string>();
            if (!string.IsNullOrEmpty(brandName))
            {
                var temp = brandName.Split('/');//拆分中英品牌名
                foreach (var o in temp)
                {
                    array.Add(o.ToLower());
                }
            }
            if (!string.IsNullOrEmpty(categoryName))
            {

            }
            if (!string.IsNullOrEmpty(itemName))
            {
                array.AddRange(Participle(itemName, true));
            }
            return string.Join(" ", array.Distinct());
        }

        private static ProductItem ConvertDocument(Document document)
        {
            var item = new ProductItem();

            item.Id = Convert.ToInt32(document.Get("Id"));
            item.Name = document.Get("Name");

            return item;
        }

        private LuceneQueryParam ConvertParam(ProductQueryParam param)
        {
            var query = new BooleanQuery();
            if (param.Page < 1)
            {
                param.Page = 1;
            }
            if (param.PageSize < 1)
            {
                param.PageSize = 50;
            }

            var target = new LuceneQueryParam(param.Page, param.PageSize, query);
            if (!string.IsNullOrEmpty(param.Keyword))
            {
                var keyword = QueryParser.Escape(param.Keyword);
                query.Add(IKQueryParser.Parse("Participle", keyword), Occur.SHOULD);

            }
            if (!string.IsNullOrEmpty(param.Brand))
            {
                var bq = new BooleanQuery();
                foreach (var o in param.Cate.Split(','))
                {
                    var t = new Term("BrandId", o);
                    bq.Add(new TermQuery(t), Occur.SHOULD);
                }
                query.Add(bq, Occur.MUST);
            }
            if (!string.IsNullOrEmpty(param.Cate))
            {
                var bq = new BooleanQuery();
                foreach (var o in param.Cate.Split(','))
                {
                    var t = new Term("CateId", o);
                    bq.Add(new TermQuery(t), Occur.SHOULD);
                }
                query.Add(bq, Occur.MUST);
            }
            if (!string.IsNullOrEmpty(param.Prop))
            {
                var bq = new BooleanQuery();
                foreach (var o in param.Cate.Split(','))
                {
                    var t = new Term("PropId", o);
                    bq.Add(new TermQuery(t), Occur.SHOULD);
                }
                query.Add(bq, Occur.MUST);
            }
            if (param.Sort != ProductQuerySort.Default)
            {
                target.Sort = new Sort(GetSortField(param.Sort));
            }
            if (param.Highlight)
            {
                target.HighlightField = "Name";
            }
            return target;
        }

        private static SortField GetSortField(ProductQuerySort sort)
        {
            if (sort == ProductQuerySort.Popularoty)
            {
                return new SortField("Popularoty", SortField.INT, true);
            }
            else if (sort == ProductQuerySort.PriceDesc)
            {
                return new SortField("Price", SortField.INT, true);
            }
            else if (sort == ProductQuerySort.PriceAsc)
            {
                return new SortField("Price", SortField.INT, false);
            }
            else if (sort == ProductQuerySort.Latest)
            {
                return new SortField("Latest", SortField.INT, true);
            }
            return new SortField("SaleQuantity", SortField.INT, true);
        }

        /*
        private ProductQueryStatistics Calculate(TopDocs topDocs)
        {

            var brands = new Dictionary<string, string>();
            var propItems = new List<ProductPropItem>();
            var categories = new Dictionary<string, Magic<string, int>>();


            var count = topDocs.TotalHits;
            for (var i = 0; i <= count; i++)
            {
                var id = topDocs.ScoreDocs[i].Doc;
                var document = searcher.Doc(id);
                CalculateBrand(document, brands);
                CalculateCategory(document, categories);


            }

            return new ProductQueryStatistics(brands, propItems);
        }

        private static void CalculateBrand(Document document, IDictionary<string, string> brands)
        {
            var id = document.Get("BrandId");
            var name = document.Get("BrandName");
            brands[id] = name;
        }

        private static void CalculateCategory(Document document, IDictionary<string, Magic<string, int>> categories)
        {
            var id = document.Get("CategoryId");
            var name = document.Get("CategoryName");
            if (categories.ContainsKey(id))
            {
                categories[id].Second += 1;
            }
            else
            {
                categories[id] = new Magic<string, int>(name, 1);
            }
        }

        private static void CalculatePropItem(Document document, IList<ProductPropItem> propItems)
        {
            var id = document.Get("PropId");
            var name = document.Get("PropStr");
            var score = CalculateScore(document);


        }


        /// <summary>
        /// 计算分数
        /// </summary>
        /// <param name="document"></param>
        /// <returns></returns>
        private static int CalculateScore(Document document)
        {
            var score = 0;
            return score;
        }

        private IList<Category> DicToList(Dictionary<string, Magic<string, int>> categories)
        {
            var list = new List<Category>();
            var temp = new List<Category>();
            Action<string, string, int> func = (ids, names, count) => {
                    var idArray = ids.Split(',');
                    var nameArray = names.Split(',');
                    var path = string.Empty;
                    var len = idArray.Length;
                    var namePath = string.Empty;
                    for (var i = 0; i < len; i++)
                    {
                        var id = idArray[i];
                        var parentId = (i == 0) ? "0" : idArray[i - 1];
                        path += id + ",";
                        var name = nameArray[i];
                        namePath += name + ",";
                        temp.Add(new Category(id, parentId, path.TrimEnd(','), name, namePath.TrimEnd(','), count));
                    }
                };
            foreach (var category in categories)
            {
                func(category.Key, category.Value.First, category.Value.Second);
            }
            foreach (var o in temp.GroupBy(x => x.Id))
            {
                var first = o.FirstOrDefault(x => x.Id == o.Key);
                if (first != null)
                {
                    var count = o.Sum(x => x.Count);
                    var selected = !string.IsNullOrEmpty(param.Category) && o.Key == param.Category;
                    list.Add(new Category(o.Key, first.ParentId, first.Path, first.Name, first.NamePath, count, selected));
                }
            }
            return list;
        }
        */
    }

    internal class Category : Hierarchy<string>
    {
        public int Count { get; set; }
        public string Name { get; set; }
        public string NamePath { get; set; }
        public bool Selected { get; set; }

        public Category()
        {

        }
        public Category(string id, string parentId, string path, string name, string namePath, int count = 1, bool selected = false)
        {
            Id = id;
            ParentId = parentId;
            Path = path;
            Name = name;
            Count = count;
            Selected = selected;
            NamePath = namePath;
            Children = new List<IHierarchy<string>>();
        }
    }

    internal static class CategoryExtension
    {
        public static IList<Category> ToTree(this List<Category> data)
        {
            Action<Category> addChildren = null;
            addChildren = (item =>
            {
                var children = data.Where(o => o.ParentId == item.Id).ToList();
                if (children.Count > 0)
                {
                    item.Children.AddRange(children);
                    foreach (var child in children)
                    {
                        addChildren(child);
                    }
                }
            });

            var root = data.Where(o => o.ParentId == "0").ToList();
            root.ForEach(o => addChildren(o));
            return root;
        }
    }
}
